package com.fit4as.fit
{

// Copyright (c) 2002 Cunningham & Cunningham, Inc.
// Released under the terms of the GNU General Public License version 2 or later.

import flash.utils.Dictionary;
import flash.utils.getDefinitionByName;
import com.fit4as.util.reflect.ClassUtil;
import com.fit4as.util.io.StringWriter;
import flash.net.getClassByAlias;


public class Fixture
{

    public var summary : HashMap = new HashMap();
    public var counts : Counts = new Counts();
    protected var args : Array;

    // Traversal //////////////////////////

    /* Altered by Rick Mugridge to dispatch on the first Fixture */
    public function doTables( tables : Parse ) : void 
    {
        summary.put("run date", new Date());
        summary.put("run elapsed time", new RunTime());
        
        if( tables == null ) return;
        
        var fixtureName : Parse = fixtureName( tables );
        if( fixtureName == null ) return; 

        try 
        {
            var fixture : Fixture = getLinkedFixtureWithArgs( tables );
            fixture.interpretTables( tables );
        } 
        catch( e : Error ) 
        {
            exception( fixtureName, e );
        }
		interpretFollowingTables( tables );        
    }

    /* Added by Rick Mugridge to allow a dispatch into DoFixture */
    protected function interpretTables( tables : Parse ) : void 
    {
        try 
        { 
        	// Don't create the first fixture again, because creation may do something important.
            getArgsForTable( tables ); // get them again for the new fixture object
            doTable( tables );
        } 
        catch( e : Error ) 
        {
            exception( fixtureName( tables ), e );
        }
    }

    /* Added by Rick Mugridge */
    protected function interpretFollowingTables( tables : Parse ) : void 
    {
        tables = tables.more;
        if( tables == null ) return;

        var fixtureName : Parse = fixtureName( tables );
        if( fixtureName != null ) 
        {
            try 
            {
                var fixture : Fixture = getLinkedFixtureWithArgs( tables );
                fixture.doTable( tables );
            } 
            catch( e : Error )
            {
                exception( fixtureName, e );
            }
        }
        interpretFollowingTables( tables );
    }

    /* Added from FitNesse*/
    protected function getLinkedFixtureWithArgs( tables : Parse ) : Fixture 
    {
        var header : Parse = fixtureName( tables );
        var fixture : Fixture = loadFixture( header.text() );
        fixture.counts = counts;
        fixture.summary = summary;
        fixture.getArgsForTable( tables );
        return fixture;
    }

    public function fixtureName( tables : Parse ) : Parse 
    {
        return tables.at( 0, 0, 0 );
    }

    public function loadFixture( fixtureName : String ) : Fixture
    {
        try 
        {
        	var classObject : Class = ClassUtil.getClassByName( fixtureName );
            return Fixture( new classObject() );
        }
        catch( e : ReferenceError )
        {
            throw new Exception( "The fixture \"" + fixtureName + "\" was not found." );
        }
        catch( e : Error )
        {
        	throw new Exception( fixtureName + "is not a fixture." );
        }
        return null;
    }

    /* Added by Rick Mugridge, from FitNesse */
    protected function getArgsForTable( table : Parse ) : void 
    {
        var argumentList : Array = new Array();
        for( var parameters : Parse = table.parts.parts.more; parameters != null; parameters = parameters.more )
        {
            argumentList.push( parameters.text() );
        }
        args = argumentList;
    }

    public function doTable( table : Parse ) : void 
    {
        //t race("Fixture#doTable");
        doRows( table.parts.more );
    }

    public function doRows( rows : Parse ) : void 
    {
        //t race("Fixture#doRow");
        while( rows != null ) 
        {
            var more : Parse = rows.more;
            doRow( rows );
            rows = more;
        }
    }

    public function doRow( row : Parse ) : void 
    {
        doCells( row.parts );
    }

    public function doCells( cells : Parse ) : void 
    {
        for( var i : int = 0; cells != null; i++ ) 
        {
            try {
                doCell( cells, i );
            } catch( e : Error ) {
                exception( cells, e );
            }
            cells = cells.more;
        }
    }

    public function doCell( cell : Parse, columnNumber : int ) : void 
    {
        right( cell );
    }


    // Annotation ///////////////////////////////

    public static var green : String 	= "#cfffcf";
    public static var red : String 		= "#ffcfcf";
    public static var gray : String 	= "#efefef";
    public static var yellow : String 	= "#ffffcf";

    public function right( cell : Parse ) : void 
    {
        //t race("Fixture#right");
        counts.right++;

		bgcolor( cell, green );
    }

    public function wrong( cell : Parse, ... rest ) : void 
    {
        counts.wrong++;

        bgcolor( cell, red );
		cell.body = escape( cell.text() );
		if( rest.length != 0 ) cell.addToBody( label("expected") + "<hr>" + escape( rest[ 0 ] ) + label("actual"));
    }

    public function info( ... rest ) : String 
    {
        //t race("info " + rest );
        if( rest.length == 1 )
        {
            return " <font color=\"#808080\">" + escape(rest[ 0 ]) + "</font>";
        }
        else if (rest.length == 2)
        {
            var cell : Parse = Parse( rest[ 0 ] );
            var message : String = String( rest[ 1 ] );
            cell.addToBody( info( message ));
        }
        return null;
    }

    public function ignore( cell : Parse ) : void 
    {
		counts.ignores++;

        bgcolor( cell, gray );
    }

    public function error( cell : Parse, message : String ) : void 
    {
        counts.exceptions++;
        
        cell.body = escape( cell.text() );
        cell.addToBody("<hr><pre>" + escape( message ) + "</pre>");
        bgcolor( cell, yellow );
        
        var sw : StringWriter = new StringWriter();
        cell.print( sw );
        trace("error " + sw.getContent() );
    }

    public function exception( cell : Parse, exception : Error ) : void 
    {
        while( exception is InvocationTargetException ) {
            exception = InvocationTargetException(exception).getTargetException();
        }
        error( cell, exception.getStackTrace() );
    }

    // Utility //////////////////////////////////

	public static function bgcolor( cell : Parse, color : String ) : void
	{
        cell.addToTag(" bgcolor=\"" + color + "\"");
	}

    public static function label( string : String ) : String 
    {
        return " <font size=-1 color=\"#c08080\"><i>" + string + "</i></font>";
    }

    public static function escape( string : String ) : String 
    {
        return string.replace(/&/g, "&amp;")
        		.replace(/</g, "&lt;")	
        		.replace(/  /g, " &nbsp;")
				.replace(/\r\n/g, "<br />")
				.replace(/\r/g, "<br />")
				.replace(/\n/g, "<br />");
    }

	/**
	 * replace spaces with camel case
	 */ 
    public static function camel( name : String ) : String 
    {
        var b : String = "";
        var t : Array = name.split(" ");
        if( t.length <= 1 ) return name;

		for( var i : int = 1; i < t.length; i++ )
		{ 
			var ti : String = t[ i ];
			t[ i ] = ti.charAt( 0 ).toUpperCase() + ti.substring( 1 );
		}
		return t.join("");
    }

	
    public function parse( s : String, type : Class ) : Object 
    {
    	switch( type )
    	{
    		case String : 
    			return s;
    			
    		case Date : 	
    			return new Date( Date.parse( s ) );
    			
    		case ScientificDouble : 
    			return ScientificDouble.valueOf(s);	
    	}
        throw new Exception("can't yet parse " + type );
    }

    public function check( ... rest) : void 
    {
    	//subclasses might (actually do) prefer a different signature
    	//therefore we keep it dynamic using ... rest
        var cell : Parse = Parse( rest[ 0 ] );
        var a : TypeAdapter = TypeAdapter( rest[ 1 ] );
        var text : String = cell.text();
        var result : Object;

        //t race("Fixture#check " + [text, value ]);
        if( text == "" ) 
        {
            try 
            {
                info( cell, a.toString( a.get() ));
            } 
            catch( e : Error )
            {
                info( cell, "error" );
            }
        } 
        else if( a == null ) 
        {
            ignore( cell );
        } 
        else if( text == "error" ) 
        {
            try 
            {
                result = a.invoke();
                wrong( cell, a.toString( result ));
            } 
            catch( e1 : IllegalAccessException ) 
            {
                exception( cell, e1 );
            } 
            catch( e2 : Error ) 
            {
                right( cell );
            }
        } 
        else 
        {
            //t race("Fixture#check ----------" + [ a, "get" in a, a.get() ]);
            try 
            {
                result = a.get();
                //t race("Fixture#check " + [result2, a.parse( text )] );
                
                if( a.equals( a.parse( text ), result )) 
                {
                    right( cell );
                } 
                else 
                {
                    wrong( cell, a.toString( result ));
                }
            } 
            catch( e : Error ) 
            {
                exception( cell, e );
            }
        }
    }

    /* Added by Rick, from FitNesse */
    public function getArgs() : Array 
    {
        return args;
    }
    
    /**
     * Let subclasses return the component type of an array
     * either a property or a return value of a method. 
     */  
    public function getComponentType( name : String ) : Class 
    {
    	return String;	
    }

}
}